refactor(session_store): thread base_dir + KeyringMode through API (#34)#43
Conversation
Option 2 follow-up to #42. Replaces the implicit `$HOME` / `AGENTKEYS_SESSION_STORE` env-var reads at every call with an explicit `SessionStore` handle that owns both. Tests construct one rooted at a per-test tempdir in file-only mode — no more `unsafe { set_var }`, no `#[serial]`, no process-global races. Changes - agentkeys-core/src/session_store.rs: introduce `SessionStore` struct with `base_dir` + `KeyringMode`, expose `new`, `file_only`, `from_env` constructors and method-form save/load/clear/list/session_path. Existing free functions (`save_session`, `load_session`, `clear_session`, `fallback_path`, `list_fallback_session_ids`, `load_session_with_legacy_fallback`) are preserved as thin wrappers that delegate to `SessionStore::from_env()`, so the daemon and any external callers keep working unchanged. Internal `marker_path` and file-I/O helpers are now methods on `SessionStore`. - agentkeys-core session_store tests: drop the `with_temp_home` mutex + `unsafe { set_var }` helper and rewrite to build a per-test `SessionStore::file_only(tmp)` directly. Fully parallel, fully hermetic. - agentkeys-cli CommandContext: add optional `session_store_override` field and `.with_session_store(store)` builder. New `session_store()` accessor returns the override or `SessionStore::from_env()`. The five `session_store::*` call sites in `cmd_init`, `cmd_revoke` (x2), `cmd_recover`, and `CommandContext::load_session` now route through it. - agentkeys-cli integration tests: delete all 13 `unsafe { set_var }` calls and all HOME mutation. Every test owns a `(SessionStore, TempDir)` pair and threads the store through context builders. Drops the flaky race surface (issue #34) by construction rather than by serialization. Why this on top of #42 - #42 patched the symptom with `#[ctor]` + `#[serial]`, which works but still compiles `unsafe { set_var }` into the test binary and requires future HOME-touching tests to remember `#[serial]`. This refactor removes the env-mutation pattern entirely — tests can't reintroduce the race because the test helpers no longer expose HOME as a knob. - Also unlocks a genuine product feature: callers can point the CLI at a non-default base directory without touching the process environment (future `AGENTKEYS_HOME` / `--data-dir` / container deploys). Test plan - `cargo test --workspace` — all 151 tests pass - `cargo test -p agentkeys-cli` × 5 consecutive runs, parallel scheduler — 37/37 each time, ~0.2–0.7s - `cargo clippy -p agentkeys-core -p agentkeys-cli --tests` — no new lints (pre-existing warnings in unrelated files unchanged) Closes #34.
…n-by-construction (codex PR #43 [P2]) Codex /codex review on PR #43 flagged that `SessionStore::new(base_dir, KeyringMode::Auto)` was unsafe: keyring entries are keyed on `session_id` alone and ignore `base_dir`, so two stores at different roots sharing a `session_id` would silently alias through the OS keychain. The newly introduced API promised per-root isolation but the `Auto` path violated that promise. Fix: remove the public `SessionStore::new(base_dir, mode)` constructor. The only public constructors are now: - `SessionStore::file_only(base_dir)` — explicit custom root, always file-only, safe by construction - `SessionStore::from_env()` — home-rooted (the single-root invariant the keyring namespace assumes), may select `KeyringMode::Auto` Custom-root callers physically cannot opt into keyring aliasing. No callers (internal or external) used `::new` with `KeyringMode::Auto` and a custom `base_dir`, so no migration is needed. Tests: `cargo test -p agentkeys-core -p agentkeys-cli` → 22 core + 37 cli, 0 failures, 0.78s. Refs #34, addresses codex [P2] on PR #43.
|
@claude review the PR |
|
Claude finished @hanwencheng's task in 2m 10s —— View job PR Review:
|
…k fallthrough (claude PR #43 review #1) The trailing `self.load(session_id)` at the end of `load_with_legacy_fallback` was redundant — the same call already failed at the top of the function, so re-running it produced the same error with no added context about which legacy fallbacks were tried. Replace with `anyhow::bail!` that names the attempted session path, the keyring mode, and the fact that legacy fallbacks only trigger for `id="master"`. Debuggability win; same observable behavior (still returns Err). Tests: `cargo test -p agentkeys-core -p agentkeys-cli` → 22 + 37, 0 failures.
Summary
Option 2 follow-up to #42 for issue #34. Replaces the implicit
\$HOME/AGENTKEYS_SESSION_STOREenv-var reads at everysession_store::*call with an explicitSessionStorehandle that ownsbase_dir+KeyringMode. Tests construct one rooted at a per-test tempdir in file-only mode — no moreunsafe { set_var }, no#[serial], no process-global races, no new dev-deps.Why this on top of #42
#42 patched the symptom with
#[ctor]+#[serial], which works but:unsafe { set_var }into the test binary (Rust 2024 flags this as unsound)#[serial]— silent flake if forgottenThis refactor removes the env-mutation pattern entirely. Tests can't reintroduce the race because the test helpers no longer expose
HOMEas a knob. It also unlocks a real product feature: callers can point the CLI at a non-default base directory without touching the process environment (future `AGENTKEYS_HOME` / `--data-dir` / container deploys).Changes
`agentkeys-core/src/session_store.rs` — introduce `SessionStore` struct with `base_dir` + `KeyringMode`, with `new` / `file_only` / `from_env` constructors and method-form `save` / `load` / `clear` / `list_ids` / `session_path` / `load_with_legacy_fallback`. Existing free functions are preserved as thin wrappers that delegate to `SessionStore::from_env()`, so the daemon and any external callers keep working unchanged. Internal `marker_path` and file-I/O helpers are now methods on `SessionStore`. The module's own tests drop the `with_temp_home` mutex + `unsafe { set_var }` helper in favor of a per-test `SessionStore::file_only(tmp)`.
`agentkeys-cli/src/lib.rs` — add optional `session_store_override` field on `CommandContext` and a `.with_session_store(store)` builder. New `session_store()` accessor returns the override or `SessionStore::from_env()`. The five `session_store::*` call sites in `cmd_init`, `cmd_revoke` (x2), `cmd_recover`, and `CommandContext::load_session` now route through it.
`agentkeys-cli/tests/cli_tests.rs` — delete all 13 `unsafe { set_var }` calls and all `HOME` mutation. Every test owns a `(SessionStore, TempDir)` pair and threads the store through context builders.
Test plan
Relationship to #42
Alternative approach — either can land. If #42 lands first, this PR will need a trivial rebase to delete the `ctor` / `serial_test` dev-deps and the `#[ctor::ctor]` block. If this lands first, #42 can be closed as superseded.
Closes #34.